package org.jsets.fastboot.routingds.config;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.sql.DataSource;
import org.jsets.fastboot.util.StringUtils;
import org.jsets.fastboot.routingds.impl.RoutingDataSource;
import org.jsets.fastboot.routingds.impl.RoutingJdbcTemplate;
import org.jsets.fastboot.routingds.impl.SwitchDataSourceAspect;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
/**
 * 可切换多数据源
 */
@Slf4j
@Configuration
@EnableConfigurationProperties({ RoutingDataSourceProperties.class })
@ConditionalOnProperty(prefix = RoutingDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = false)
@AutoConfigureBefore(name= {
		"org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration",
		"com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure",
		"com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration"
})
public class RoutingDataSourceAutoConfiguration {

	@Autowired
	private RoutingDataSourceProperties properties;

	@Bean
	@ConditionalOnMissingBean
	public DataSource dataSource() {
		if (null == this.properties.getItems() || this.properties.getItems().isEmpty()) {
			throw new BeanCreationException("multiple-ds 配置错误");
		}

		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
		DataSource primaryDataSource = null;
		int primaryAmount = 0;
		for (DruidProperties p : this.properties.getItems()) {
			DataSource dds = null;
			if (this.properties.isUseAtomikos()) {
				dds = this.buildAtomikosDataSource(p);
			} else {
				dds = this.buildDruidDataSource(p);
			}

			String name = p.getName();
			if (Strings.isNullOrEmpty(name)) {
				name = p.getUrl();
			}

			if (p.isPrimary()) {
				primaryDataSource = dds;
				primaryAmount++;
			}

			log.info("设置数据源： {}", name);
			targetDataSources.put(name, dds);
		}
		if (1 != primaryAmount) {
			throw new BeanCreationException("请设置一个主数据源");
		}

		DataSourceHolder.setPrimaryDataSource(primaryDataSource);
		DataSourceHolder.setDataSources(targetDataSources);

		// 构造RoutingDataSource
		RoutingDataSource multipleDataSource = new RoutingDataSource();
		multipleDataSource.setTargetDataSources(targetDataSources);
		multipleDataSource.setDefaultTargetDataSource(primaryDataSource);
		return multipleDataSource;
	}

	@Bean
	public RoutingJdbcTemplate routingJdbcTemplate() {
		return new RoutingJdbcTemplate();
	}

	@Bean
	public SwitchDataSourceAspect switchDataSourceAspect() {
		return new SwitchDataSourceAspect();
	}

	private DataSource buildDruidDataSource(DruidProperties p) {
		DruidDataSource dds = new DruidDataSource();
		dds.setUrl(p.getUrl());
		dds.setUsername(p.getUsername());
		dds.setPassword(p.getPassword());
		dds.setDriverClassName(p.getDriverClassName());
		dds.setInitialSize(p.getInitialSize());
		dds.setMinIdle(p.getMinIdle());
		dds.setMaxActive(p.getMaxActive());
		dds.setMaxWait(p.getMaxWait());
		dds.setTimeBetweenEvictionRunsMillis(p.getTimeBetweenEvictionRunsMillis());
		dds.setMinEvictableIdleTimeMillis(p.getMinEvictableIdleTimeMillis());
		dds.setValidationQuery(p.getValidationQuery());
		dds.setTestWhileIdle(p.isTestWhileIdle());
		dds.setTestOnBorrow(p.isTestOnBorrow());
		dds.setTestOnReturn(p.isTestOnReturn());
		dds.setUseGlobalDataSourceStat(true);

		if (Objects.nonNull(p.getPoolPreparedStatements())) {
			dds.setPoolPreparedStatements(p.getPoolPreparedStatements());
		}
		if (Objects.nonNull(p.getMaxPoolPreparedStatementPerConnectionSize())) {
			dds.setMaxPoolPreparedStatementPerConnectionSize(p.getMaxPoolPreparedStatementPerConnectionSize());
		}
		String filters = this.properties.getFilters();
		if (StringUtils.notEmpty(filters)) {
			try {
				dds.setFilters(filters);
			} catch (SQLException e) {
				log.error(e.getMessage(), e);
			}
		}

		return dds;
	}

	private DataSource buildAtomikosDataSource(DruidProperties p) {
		DruidXADataSource dds = new DruidXADataSource();
		dds.setUrl(p.getUrl());
		dds.setUsername(p.getUsername());
		dds.setPassword(p.getPassword());
		dds.setDriverClassName(p.getDriverClassName());
		dds.setInitialSize(p.getInitialSize());
		dds.setMinIdle(p.getMinIdle());
		dds.setMaxActive(p.getMaxActive());
		dds.setMaxWait(p.getMaxWait());
		dds.setTimeBetweenEvictionRunsMillis(p.getTimeBetweenEvictionRunsMillis());
		dds.setMinEvictableIdleTimeMillis(p.getMinEvictableIdleTimeMillis());
		dds.setValidationQuery(p.getValidationQuery());
		dds.setTestWhileIdle(p.isTestWhileIdle());
		dds.setTestOnBorrow(p.isTestOnBorrow());
		dds.setTestOnReturn(p.isTestOnReturn());
		dds.setUseGlobalDataSourceStat(true);

		if (Objects.nonNull(p.getPoolPreparedStatements())) {
			dds.setPoolPreparedStatements(p.getPoolPreparedStatements());
		}
		if (Objects.nonNull(p.getMaxPoolPreparedStatementPerConnectionSize())) {
			dds.setMaxPoolPreparedStatementPerConnectionSize(p.getMaxPoolPreparedStatementPerConnectionSize());
		}
		String filters = this.properties.getFilters();
		if (StringUtils.notEmpty(filters)) {
			try {
				dds.setFilters(filters);
			} catch (SQLException e) {
				log.error(e.getMessage(), e);
			}
		}

		AtomikosDataSourceBean atomikosDataSource = new AtomikosDataSourceBean();
		atomikosDataSource.setUniqueResourceName(p.getName());
		atomikosDataSource.setXaDataSource(dds);
		atomikosDataSource.setTestQuery("SELECT 1");
		atomikosDataSource.setMinPoolSize(2);
		atomikosDataSource.setMaxPoolSize(20);
		atomikosDataSource.setBorrowConnectionTimeout(60);
		return atomikosDataSource;
	}
}